﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Collections.Observable.DebugViews;
using System.ComponentModel;
using System.Collections;

namespace System.Collections.Observable
{
    [DebuggerTypeProxy(typeof(ObservableDictionaryBaseDebugView<,,>))]
    [DebuggerDisplay("Count = {Count}")]
    [Serializable]
    public class ObservableDictionaryBase<TKey, TValue, TDict> : ObservableCollectionBase<KeyValuePair<TKey,TValue>, TDict>,
                                                          IObservableDictionary<TKey, TValue>
        where TDict: IDictionary<TKey,TValue>
    {

        public ObservableDictionaryBase(TDict dictionary):base(dictionary)
        {
            
        }

        #region PropertyChanged event related functionality

        protected override bool OnIsPropertyChangedSupported()
        {
            return typeof(INotifyPropertyChanged).IsAssignableFrom(typeof(TValue));
        }

        protected override void OnSubscribePropertyChanged(IEnumerable values)
        {
            foreach (KeyValuePair<TKey,TValue> pair in values)
            {
                INotifyPropertyChanged o = pair.Value as INotifyPropertyChanged;

                if (o != null)
                {
                    o.PropertyChanged += OnPropertyChanged;
                }
            }
        }

        protected override void OnUnsubscribePropertyChanged(IEnumerable values)
        {
            foreach (KeyValuePair<TKey, TValue> pair in values)
            {
                INotifyPropertyChanged o = pair as INotifyPropertyChanged;

                if (o != null)
                {
                    o.PropertyChanged -= OnPropertyChanged;
                }
            }
        }

        #endregion

        #region IDictionary<TKey,TValue> interface implementation

        // Summary:
        //     Gets an System.Collections.Generic.ICollection<T> containing the keys of
        //     the System.Collections.Generic.IDictionary<TKey,TValue>.
        //
        // Returns:
        //     An System.Collections.Generic.ICollection<T> containing the keys of the object
        //     that implements System.Collections.Generic.IDictionary<TKey,TValue>.
        public ICollection<TKey> Keys 
        {
            get
            {
                return InternalCollection.Keys;
            }
        }

        // Summary:
        //     Gets an System.Collections.Generic.ICollection<T> containing the values in
        //     the System.Collections.Generic.IDictionary<TKey,TValue>.
        //
        // Returns:
        //     An System.Collections.Generic.ICollection<T> containing the values in the
        //     object that implements System.Collections.Generic.IDictionary<TKey,TValue>.
        public ICollection<TValue> Values 
        {
            get
            {
                return InternalCollection.Values;
            }
        }

        // Summary:
        //     Gets or sets the element with the specified key.
        //
        // Parameters:
        //   key:
        //     The key of the element to get or set.
        //
        // Returns:
        //     The element with the specified key.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     key is null.
        //
        //   System.Collections.Generic.KeyNotFoundException:
        //     The property is retrieved and key is not found.
        //
        //   System.NotSupportedException:
        //     The property is set and the System.Collections.Generic.IDictionary<TKey,TValue>
        //     is read-only.
        public TValue this[TKey key] 
        {
            get
            {
                return InternalCollection[key];
            }

            set
            {
                NotifyCollectionChangedEventArgs args;
                if (InternalCollection.Keys.Contains(key))
                {
                    //replace
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, 
                        new KeyValuePair<TKey,TValue>(key, value), 
                        new KeyValuePair<TKey,TValue>(key, InternalCollection[key]) );
                }
                else
                {
                    //add
                    args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, 
                                                                new KeyValuePair<TKey,TValue>(key, value) );
                }

                InternalCollection[key] = value;
                OnCollectionChanged(args);
            }
        }

        // Summary:
        //     Adds an element with the provided key and value to the System.Collections.Generic.IDictionary<TKey,TValue>.
        //
        // Parameters:
        //   key:
        //     The object to use as the key of the element to add.
        //
        //   value:
        //     The object to use as the value of the element to add.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     key is null.
        //
        //   System.ArgumentException:
        //     An element with the same key already exists in the System.Collections.Generic.IDictionary<TKey,TValue>.
        //
        //   System.NotSupportedException:
        //     The System.Collections.Generic.IDictionary<TKey,TValue> is read-only.
        public void Add(TKey key, TValue value)
        {
            InternalCollection.Add(key, value);
            OnCollectionChanged( new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, 
                                                                      new KeyValuePair<TKey,TValue>(key, value) ));
        }

        // Summary:
        //     Determines whether the System.Collections.Generic.IDictionary<TKey,TValue>
        //     contains an element with the specified key.
        //
        // Parameters:
        //   key:
        //     The key to locate in the System.Collections.Generic.IDictionary<TKey,TValue>.
        //
        // Returns:
        //     true if the System.Collections.Generic.IDictionary<TKey,TValue> contains
        //     an element with the key; otherwise, false.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     key is null.
        public bool ContainsKey(TKey key)
        {
            return InternalCollection.ContainsKey(key);
        }
        
        // Summary:
        //     Removes the element with the specified key from the System.Collections.Generic.IDictionary<TKey,TValue>.
        //
        // Parameters:
        //   key:
        //     The key of the element to remove.
        //
        // Returns:
        //     true if the element is successfully removed; otherwise, false. This method
        //     also returns false if key was not found in the original System.Collections.Generic.IDictionary<TKey,TValue>.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     key is null.
        //
        //   System.NotSupportedException:
        //     The System.Collections.Generic.IDictionary<TKey,TValue> is read-only.
        public bool Remove(TKey key)
        {
            if (InternalCollection.Keys.Contains(key))
            {
                TValue value = InternalCollection[key];
                InternalCollection.Remove(key);

                OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,
                                                                            new KeyValuePair<TKey, TValue>(key, value)));
            }

            return InternalCollection.Remove(key);
        }

        // Summary:
        //     Gets the value associated with the specified key.
        //
        // Parameters:
        //   key:
        //     The key whose value to get.
        //
        //   value:
        //     When this method returns, the value associated with the specified key, if
        //     the key is found; otherwise, the default value for the type of the value
        //     parameter. This parameter is passed uninitialized.
        //
        // Returns:
        //     true if the object that implements System.Collections.Generic.IDictionary<TKey,TValue>
        //     contains an element with the specified key; otherwise, false.
        //
        // Exceptions:
        //   System.ArgumentNullException:
        //     key is null.
        public bool TryGetValue(TKey key, out TValue value)
        {
            return InternalCollection.TryGetValue(key, out value);
        }

        #endregion
    }
}
